package org.zjvis.datascience.common.model;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zjvis.datascience.common.sql.DataCleanSqlHelper;
import org.zjvis.datascience.common.sql.SqlHelper;
import org.zjvis.datascience.common.util.SqlUtil;

import java.util.List;
import java.util.Map;

/**
 * @description 复杂聚合查询包装类 用于拼装生成SQL
 * @date 2021-12-23
 */
@Data
public class AggrConfig {

    private List<DimensionConfig> dimensions;

    private List<MeasureConfig> measures;

    private List<ConfigComponent> filters;

    private Integer limit;

    private final static Logger logger = LoggerFactory.getLogger(AggrConfig.class);

    private static List<ConfigComponent> parseDatasetJson(JSONObject dsJson) {
        List<ConfigComponent> filters = Lists.newArrayList();
        JSONArray filterArray = dsJson.getJSONArray("filter");
        if (filterArray != null) {
            for (int i = 0; i < filterArray.size(); i++) {
                JSONArray subFilterArray = (JSONArray) filterArray.get(i);
                ConfigComponent component = null;
                if (subFilterArray.size() > 1) {
                    CompositeConfig composite = new CompositeConfig();
                    composite.setType("AND");
                    for (int j = 0; j < subFilterArray.size(); j++) {
                        JSONObject jsonObj = subFilterArray.getJSONObject(j);
                        DimensionConfig dc = new DimensionConfig();
                        dc.setFieldName(jsonObj.getString("fieldName"));
                        dc.setFilterType(jsonObj.getString("condition"));
                        dc.setValues(Lists.newArrayList(jsonObj.getString("value")));
                        composite.add(dc);
                    }
                    component = composite;
                } else {
                    if (subFilterArray.size() > 0) {
                        JSONObject jsonObj = subFilterArray.getJSONObject(0);
                        DimensionConfig dc = new DimensionConfig();
                        dc.setFieldName(jsonObj.getString("fieldName"));
                        dc.setFilterType(jsonObj.getString("condition"));
                        dc.setValues(Lists.newArrayList(jsonObj.getString("value")));
                        component = dc;
                    }
                }

                if (component != null) {
                    filters.add(component);
                }
            }
        }
        return filters;
    }

    /**
     * 使用SQLHelper 需要一个bindMap
     *
     * @param array
     * @return
     */
    private static Map<String, Integer> genBindMap(JSONArray array) {
        Map<String, Integer> bindMap = Maps.newHashMap();
        array.forEach(item -> {
            String col = ((JSONObject) item).getString("col");
            Integer type = SqlUtil.encodeType(((JSONObject) item).getString("desc"));
            bindMap.put(col.toUpperCase(), type);
        });
        return bindMap;
    }

    private static List<ConfigComponent> parseWidgetJson(AggrConfig aggrConfig, JSONObject widgetJson) {
        JSONObject wdJson = widgetJson.getJSONObject("config");
        List<ConfigComponent> filters = parseFilter(wdJson.getJSONArray("filters"));
        Map<String, Integer> bindMap = genBindMap(wdJson.getJSONArray("filters"));
        SqlHelper sqlHelper = new SqlHelper();
        sqlHelper.bind(bindMap);
        String where = sqlHelper.assembleFilter(filters);
        JSONArray groupArray = wdJson.getJSONArray("groups");
        List<DimensionConfig> groups = Lists.newArrayList();
        if (groupArray != null) {
            for (int i = 0; i < groupArray.size(); i++) {
                JSONObject groupObj = groupArray.getJSONObject(i);
                DimensionConfig dc = wrapDim(groupObj);
                dc.setType("group");
                groups.add(dc);
            }
        }

        JSONArray keyArray = wdJson.getJSONArray("keys");
        List<DimensionConfig> dims = Lists.newArrayList();
        if (keyArray != null) {
            for (int i = 0; i < keyArray.size(); i++) {
                JSONObject keyObj = keyArray.getJSONObject(i);
                DimensionConfig dc = wrapDim(keyObj);
                dc.setType("key");
                dims.add(dc);
            }
        }

        aggrConfig.setDimensions(ListUtils.union(dims, groups));

        JSONObject fields = widgetJson.getJSONObject("fields");
        Map<String, JSONObject> expMap = Maps.newHashMap();
        if (fields != null) {
            JSONArray expArray = fields.getJSONArray("expression");
            if (expArray != null) {
                for (int i = 0; i < expArray.size(); i++) {
                    JSONObject jsonObj = expArray.getJSONObject(i);
                    expMap.put(jsonObj.getString("alias"), jsonObj);
                }
            }
            JSONArray dimArray = fields.getJSONArray("dimension");
            if (dimArray != null) {
                for (int i = 0; i < dimArray.size(); i++) {
                    JSONObject jsonObj = dimArray.getJSONObject(i);
                    String name = jsonObj.getString("name");
                    String alias = jsonObj.getString("alias");
                    if (name != alias) {
                        JSONObject tmpJsonObj = new JSONObject();
                        tmpJsonObj.put("alias", alias);
                        tmpJsonObj.put("exp", name);
                        tmpJsonObj.put("name", alias);
                        tmpJsonObj.put("type", "field");
                        expMap.put(alias, tmpJsonObj);
                    }
                }
            }
        }

        JSONArray valueArray = wdJson.getJSONArray("values");
        List<MeasureConfig> mcs = Lists.newArrayList();
        if (valueArray != null) {
            for (int i = 0; i < valueArray.size(); i++) {
                JSONObject valueObj = valueArray.getJSONObject(i);
                MeasureConfig mc = wrapMeasure(valueObj, expMap);
                if (mc != null) {
                    mcs.add(mc);
                }
            }
        }

        aggrConfig.setMeasures(mcs);
        return filters;
    }


    public static AggrConfig parse(String datasetJson, JSONObject widgetJson) {
        AggrConfig aggrConfig = new AggrConfig();

        List<ConfigComponent> filters = null;

        if (StringUtils.isNotEmpty(datasetJson)) {
            aggrConfig.setDimensions(Lists.newArrayList());
            aggrConfig.setMeasures(Lists.newArrayList());
            filters = parseDatasetJson(JSONObject.parseObject(datasetJson));
        } else if (ObjectUtil.isNotNull(widgetJson)) {
            Integer topNValue = widgetJson.getJSONObject("config").getInteger("topN");
            if (ObjectUtil.isNotNull(topNValue)) {
                aggrConfig.setLimit(topNValue);
            } else {
                aggrConfig.setLimit(50);
            }
            filters = parseWidgetJson(aggrConfig, widgetJson);
        }

        aggrConfig.setFilters(filters);
        return aggrConfig;
    }

    private static DimensionConfig wrapDim(JSONObject jsonObj) {
        if (jsonObj == null) {
            return null;
        }

        DimensionConfig dim = new DimensionConfig();

        String fieldName = jsonObj.getString("col");
        dim.setFieldName(fieldName);

        String filterType = jsonObj.getString("filterType");
        if (StringUtils.isNotEmpty(filterType)) {
            dim.setFilterType(filterType);
        }
        if (jsonObj.getJSONArray("values") != null) {
            List<String> values = jsonObj.getJSONArray("values").toJavaList(String.class);
            if (CollectionUtils.isNotEmpty(values)) {
                dim.setValues(values);
            }
        }
        String sort = jsonObj.getString("sort");
        if (StringUtils.isNotEmpty(sort)) {
            dim.setSort(sort);
        }
        String alias = jsonObj.getString("alias");
        if (StringUtils.isNotEmpty(alias)) {
            dim.setAlias(alias);
        }


        JSONObject funObj = jsonObj.getJSONObject("fun");
        if (funObj != null) {
            String funName = funObj.getString("name");
            JSONArray paramArray = funObj.getJSONArray("params");
            if (StringUtils.isNotEmpty(funName)) {
                FunctionConfig funConf = new FunctionConfig(funName, paramArray.toArray());
                dim.setFun(funConf);
            }
        }

        return dim;
    }

    private static MeasureConfig wrapMeasure(JSONObject jsonObj, Map<String, JSONObject> expMap) {
        if (jsonObj == null) {
            return null;
        }
        MeasureConfig mc = new MeasureConfig();

        String alias = jsonObj.getString("col");
        if (jsonObj.containsKey("aggregate_type")) {
            mc.setAggType(jsonObj.getString("aggregate_type"));
        } else {
            mc.setAggType("count");
        }
        mc.setAlias(alias);
        mc.setSort(jsonObj.getString("sort"));
        mc.setTop(jsonObj.getInteger("topN"));
        if (expMap == null || expMap.size() == 0) {
            mc.setFieldName(alias);
        } else {
            JSONObject expJson = expMap.get(alias);
            if (expJson != null) {
                String type = expJson.getString("type");
                if ("function".equals(type)) {
                    mc.setExpr(expJson.getString("exp"));
                } else if ("field".equals(type)) {
                    mc.setFieldName(expJson.getString("exp"));
                }
            }
        }
        return mc;
    }


    public static List<ConfigComponent> parseFilter(JSONArray filterArray) {
        List<ConfigComponent> filters = Lists.newArrayList();
        if (filterArray != null && !filterArray.isEmpty()) {
            for (int i = 0; i < filterArray.size(); i++) {
                JSONObject jsonObj = filterArray.getJSONObject(i);
                //不存在 或者 存在时 值为 true
                if (!jsonObj.containsKey("enabled") || jsonObj.getBoolean("enabled")) {
                    filters.add(DataCleanSqlHelper.resolveSingle(jsonObj));
                }
            }
        }
        return filters;
    }

}
